package com.tuniu.agents.tools.hotel.tool;

import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.tuniu.agents.advisor.ChatEnvironmentMemory;
import com.tuniu.agents.common.AgentsConstants;
import com.tuniu.agents.message.ToolLoggerInfo;
import com.tuniu.agents.tools.hotel.HotelProperties;
import com.tuniu.agents.tools.hotel.constant.HotelConstants;
import com.tuniu.agents.util.ConversationUtil;
import com.tuniu.agents.tools.hotel.utils.HotelCommonUtil;
import com.tuniu.agents.util.HttpManager;
import com.tuniu.agents.util.JsonUtil;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ToolContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.BiFunction;

/**
 * Tool for retrieving the lowest price hotel list near a POI (Point of Interest)
 */
public class NlowestPriceHotelList implements BiFunction<NlowestPriceHotelList.Request, ToolContext, NlowestPriceHotelList.Response> {

    private static final Logger log = LoggerFactory.getLogger(NlowestPriceHotelList.class);

    public static final String TOOLNAME = "hotel_poi_list";

    @Autowired
    private HttpManager httpManager;

    @Autowired
    private ChatEnvironmentMemory chatEnvironmentMemory;

    private final String url;
    private final String accessKey;

    /**
     * Constructor that initializes the hotel POI list URL and access key
     * 
     * @param properties Hotel properties containing configuration
     */
    public NlowestPriceHotelList(HotelProperties properties) {
        this.url = properties.getHotelPoiListUrl();
        this.accessKey = properties.getAccessKey();
    }

    @ToolLoggerInfo(beanName = "hotelToolLoggingService")
    @Override
    public Response apply(Request request, ToolContext toolContext) {
        Request newRequest = null;
        try {
            log.info("NlowestPriceHotelList input:{}", JsonUtil.toJson(request));
            newRequest = processRequestParameters(request, toolContext);
            addChatEnvironmentMemory(ConversationUtil.getConversationId(toolContext), newRequest);
            return httpManager.post(url, newRequest, Response.class, Map.of("accessKey", accessKey));
        } catch (Exception e) {
            log.error("NlowestPriceHotelList apply error request:{}", JsonUtil.toJson(newRequest), e);
            return null;
        }
    }

    /**
     * Process and prepare request parameters with default values if needed
     * 
     * @param request Original request
     * @param toolContext Tool context
     * @return Processed request with filled parameters
     */
    private Request processRequestParameters(Request request, ToolContext toolContext) {
        return new Request(
                HotelCommonUtil.getDefaultCityName(request.cityName(), request.poiName(), toolContext),
                request.checkIn(),
                request.checkOut(),
                request.poiName(),
                request.queryId(),
                request.pageNum(),
                request.adultNum(),
                request.childNum(),
                request.keyword(),
                request.prices(),
                buildUserLocation(toolContext)
        );
    }

    /**
     * Build user location information from the tool context
     * 
     * @param toolContext Tool context containing user location data
     * @return Location object with user's city and coordinates
     */
    private Location buildUserLocation(ToolContext toolContext) {
        return new Location(
                ConversationUtil.getUserCityCode(toolContext),
                ConversationUtil.getUserCityName(toolContext),
                ConversationUtil.getUserLng(toolContext),
                ConversationUtil.getUserLat(toolContext)
        );
    }

    /**
     * Build a new hotel data abbreviation with URL information
     * 
     * @param hotelDataAbbr Original hotel data
     * @return Updated hotel data with URL information
     */
    public static HotelDataAbbr buildNewData(HotelDataAbbr hotelDataAbbr) {
        return new HotelDataAbbr(
                hotelDataAbbr.hotelId(),
                hotelDataAbbr.chineseName(),
                hotelDataAbbr.starName(),
                hotelDataAbbr.address(),
                hotelDataAbbr.districtName(),
                hotelDataAbbr.brandName(),
                hotelDataAbbr.groupName(),
                hotelDataAbbr.commentScore(),
                hotelDataAbbr.lowestPrice(),
                hotelDataAbbr.counts(),
                hotelDataAbbr.comment(),
                hotelDataAbbr.commentDigest(),
                hotelDataAbbr.firstPic(),
                hotelDataAbbr.business(),
                hotelDataAbbr.roomName(),
                hotelDataAbbr.roomArea(),
                hotelDataAbbr.roomWindow(),
                hotelDataAbbr.meal(),
                hotelDataAbbr.refund(),
                hotelDataAbbr.cityName(),
                hotelDataAbbr.cityCode(),
                hotelDataAbbr.checkIn(),
                hotelDataAbbr.checkOut(),
                buildNewCommonUrl(hotelDataAbbr),
                hotelDataAbbr.domestic(),
                hotelDataAbbr.cityType(),
                hotelDataAbbr.distance()
        );
    }

    /**
     * Build common URL object with app and mini-program URLs
     * 
     * @param hotelDataAbbr Hotel data
     * @return Common URL object with relevant links
     */
    private static CommonUrl buildNewCommonUrl(HotelDataAbbr hotelDataAbbr) {
        return new CommonUrl(
                buildAppDetailUrl(hotelDataAbbr),
                Strings.EMPTY,
                buildMpDetailUrl(hotelDataAbbr)
        );
    }

    /**
     * Build app detail URL for the hotel
     * 
     * @param hotelDataAbbr Hotel data
     * @return App detail URL
     */
    private static String buildAppDetailUrl(HotelDataAbbr hotelDataAbbr) {
        try {
            Map<String, Object> commonParams = buildCommonDetailParams(hotelDataAbbr);
            commonParams.put("childAges", HotelConstants.DEFAULT_CHILD_AGES);
            commonParams.put("sourceType", 1);
            commonParams.put("isGlobal", Boolean.FALSE.equals(hotelDataAbbr.domestic()) ? 1 : 0);
            commonParams.put("cityCode", hotelDataAbbr.cityCode());
            commonParams.put("cityType", hotelDataAbbr.cityType());
            commonParams.put("lowestPrice", Optional.ofNullable(hotelDataAbbr.lowestPrice()).map(Double::intValue).orElse(0));
            commonParams.put("traceId", UUID.randomUUID().toString());

            Map<String, Object> urlParams = new HashMap<>();
            urlParams.put("rctModule", "tnhotel");
            urlParams.put("rctModuleName", "hotelDetail");
            urlParams.put("rctModuleParams", commonParams);

            String jsonParams = JsonUtil.toJson(urlParams);
            String encodedParams = URLEncoder.encode(jsonParams, StandardCharsets.UTF_8);
            return HotelConstants.APP_POI_DETAIL_DETAIL_PREFIX + encodedParams;
        } catch (Exception e) {
            log.error("Failed to generate App detail link", e);
        }
        return Strings.EMPTY;
    }

    /**
     * Build mini-program detail URL for the hotel
     * 
     * @param hotelDataAbbr Hotel data
     * @return Mini-program detail URL
     */
    private static String buildMpDetailUrl(HotelDataAbbr hotelDataAbbr) {
        try {
            Map<String, Object> commonParams = buildCommonDetailParams(hotelDataAbbr);
            commonParams.put("childAges", Strings.EMPTY);
            Map<String, Object> locationParams = new HashMap<>();
            locationParams.put("cityCode", hotelDataAbbr.cityCode());
            locationParams.put("cityName", hotelDataAbbr.cityName());
            locationParams.put("cityType", hotelDataAbbr.cityType());
            commonParams.put("curUserLocation", locationParams);

            String jsonParams = JsonUtil.toJson(commonParams);
            String encodedParams = URLEncoder.encode(jsonParams, StandardCharsets.UTF_8);
            return HotelConstants.MP_DETAIL_URL_PREFIX + encodedParams;
        } catch (Exception e) {
            log.error("Failed to generate mini-program detail link", e);
        }
        return Strings.EMPTY;
    }

    /**
     * Build common parameters for detail URLs
     * 
     * @param hotelDataAbbr Hotel data
     * @return Map of common parameters
     */
    private static Map<String, Object> buildCommonDetailParams(HotelDataAbbr hotelDataAbbr) {
        Map<String, Object> params = new HashMap<>();
        params.put("hotelId", hotelDataAbbr.hotelId());
        params.put("checkIn", hotelDataAbbr.checkIn());
        params.put("checkOut", hotelDataAbbr.checkOut());
        params.put("roomNum", HotelConstants.DEFAULT_ROOM_NUM);
        params.put("adultNum", HotelConstants.DEFAULT_ADULT_NUM);
        params.put("childNum", HotelConstants.DEFAULT_CHILD_NUM);
        params.put("rid", HotelConstants.DEFAULT_RID);
        params.put("pid", HotelConstants.DEFAULT_PID);
        return params;
    }

    /**
     * Add request information to chat environment memory
     * 
     * @param conversationId Conversation ID
     * @param request Request object
     */
    private void addChatEnvironmentMemory(String conversationId, Request request) {
        Map<String, Object> map = new HashMap<>();
        map.put(conversationId + "_LAST_ACTION","nearby_hotel_query");
        map.put(conversationId + "_request",request);
        if (StringUtils.hasText(request.cityName())){
            map.put(AgentsConstants.AgentsConversationEnvironmentKey.PARAM_DEPARTURE_CITY_NAME,request.cityName());
        }
        chatEnvironmentMemory.add(conversationId, map);
    }

    @JsonClassDescription("poi酒店列表查询参数")
    public record Request(
            @JsonProperty(value = "cityName") @JsonPropertyDescription("城市名称") String cityName,
            @JsonProperty(required = true, value = "checkIn") @JsonPropertyDescription("入住日期,格式YYYY-DD,示例2025-10-01") String checkIn,
            @JsonProperty(required = true, value = "checkOut") @JsonPropertyDescription("离店日期,格式YYYY-DD,示例2025-10-01") String checkOut,
            @JsonProperty(value = "poiName") @JsonPropertyDescription("POI名称") String poiName,
            @JsonProperty(value = "queryId") @JsonPropertyDescription("查询快照ID") String queryId,
            @JsonProperty(value = "pageNum") @JsonPropertyDescription("页码") Integer pageNum,
            @JsonProperty(value = "adultNum") @JsonPropertyDescription("成人数") Integer adultNum,
            @JsonProperty(value = "childNum") @JsonPropertyDescription("儿童数") Integer childNum,
            @JsonProperty(value = "keyword") @JsonPropertyDescription("其他信息,例如：品牌,星级,配套设施,酒店特色,用户出行目的（例如：出差、亲子游、度蜜月）") String keyword,
            @JsonProperty(value = "prices") @JsonPropertyDescription("价格") String prices,
            @JsonProperty(value = "userLocation") @JsonPropertyDescription("用户定位信息") Location userLocation) {
    }

    @JsonClassDescription("用户定位信息")
    public record Location(
            @JsonProperty(value = "cityCode") @JsonPropertyDescription("城市code") String cityCode,
            @JsonProperty(value = "cityName") @JsonPropertyDescription("城市名称") String cityName,
            @JsonProperty(value = "lng") @JsonPropertyDescription("用户经度") Double lng,
            @JsonProperty(value = "lat") @JsonPropertyDescription("用户纬度") Double lat) {
    }

    @JsonClassDescription("poi酒店列表查询返参")
    public record Response(
            @JsonProperty(required = true,
                    value = "successCode") @JsonPropertyDescription("成功标识，true：\"查询成功！\"，false：\"查询失败！\"") Boolean successCode,
            @JsonProperty(value = "queryId") @JsonPropertyDescription("查询快照ID") String queryId,
            @JsonProperty(value = "totalPageNum") @JsonPropertyDescription("查询ID") Integer totalPageNum,
            @JsonProperty(value = "data") @JsonPropertyDescription("酒店列表数据") List<HotelDataAbbr> data) {
    }

    @JsonClassDescription("酒店列表数据")
    public record HotelDataAbbr(
            @JsonProperty(value = "hotelId") @JsonPropertyDescription("酒店Id") Long hotelId,
            @JsonProperty(value = "chineseName") @JsonPropertyDescription("酒店中文名称") String chineseName,
            @JsonProperty(value = "starName") @JsonPropertyDescription("酒店星级") String starName,
            @JsonProperty(value = "address") @JsonPropertyDescription("酒店地址") String address,
            @JsonProperty(value = "districtName") @JsonPropertyDescription("酒店地区名称") String districtName,
            @JsonProperty(value = "brandName") @JsonPropertyDescription("品牌名称") String brandName,
            @JsonProperty(value = "groupName") @JsonPropertyDescription("集团名称") String groupName,
            @JsonProperty(value = "commentScore") @JsonPropertyDescription("评分") Double commentScore,
            @JsonProperty(value = "lowestPrice") @JsonPropertyDescription("最低价") Double lowestPrice,
            @JsonProperty(value = "counts") @JsonPropertyDescription("数量") Integer counts,
            @JsonProperty(value = "comment") @JsonPropertyDescription("评论") String comment,
            @JsonProperty(value = "commentDigest") @JsonPropertyDescription("评论简述") String commentDigest,
            @JsonProperty(value = "firstPic") @JsonPropertyDescription("首图地址") String firstPic,
            @JsonProperty(value = "business") @JsonPropertyDescription("商圈") String business,
            @JsonProperty(value = "roomName") @JsonPropertyDescription("房型名称") String roomName,
            @JsonProperty(value = "roomArea") @JsonPropertyDescription("房型面积") String roomArea,
            @JsonProperty(value = "roomWindow") @JsonPropertyDescription("房型窗型") String roomWindow,
            @JsonProperty(value = "meal") @JsonPropertyDescription("餐食") String meal,
            @JsonProperty(value = "refund") @JsonPropertyDescription("取消规则") String refund,
            @JsonProperty(value = "cityName") @JsonPropertyDescription("城市名称") String cityName,
            @JsonProperty(value = "cityCode") @JsonPropertyDescription("城市code") String cityCode,
            @JsonProperty(value = "checkIn") @JsonPropertyDescription("入住日期") String checkIn,
            @JsonProperty(value = "checkOut") @JsonPropertyDescription("离店日期") String checkOut,
            @JsonProperty(value = "url") @JsonPropertyDescription("url地址") CommonUrl url,
            @JsonProperty(value = "domestic") @JsonPropertyDescription("是否国内") Boolean domestic,
            @JsonProperty(value = "cityType") @JsonPropertyDescription("城市类型") Integer cityType,
            @JsonProperty(value = "distance") @JsonPropertyDescription("距离") String distance) {
    }

    @JsonClassDescription("url地址")
    public record CommonUrl(
            @JsonProperty(value = "appUrl") @JsonPropertyDescription("appUrl") String appUrl,
            @JsonProperty(value = "mUrl") @JsonPropertyDescription("m站url") String mUrl,
            @JsonProperty(value = "mpUrl") @JsonPropertyDescription("微信小程序Url") String mpUrl) {
    }

}
